import opus from "../wasm/opus";
import CompoUint8Array from '../utils/weila_compositor_array';
import {WeilaRingBuffer} from "../utils/weila_ringbuffer";
import {buildPlayerReq, buildPlayerRsp, PlayEvent} from "./player_constant";

const PLAY_OPENING = 0;
const PLAY_OPENED = 1;
const PLAY_CLOSING = 2;
const PLAY_CLOSED = 3;
const PLAY_STARTING = 4;
const PLAY_STARTED = 5;
const PLAY_PAUSING = 6;
const PLAY_PAUSED = 7;
const PLAY_RESUMING = 8;
const PLAY_STOPPING = 9;
const PLAY_STOPPED = 10;

class WLPlayerWorklet extends AudioWorkletProcessor {
    constructor(options) {
        super(options);
        console.log("WLPlayerWorklet=================>", options);
        this.opusModule = null;
        this.decoder = null;
        this.pcmBuffer = 0;
        this.decoder = 0;
        this.encodedData = 0;
        this.sampleRate = options.processorOptions.sampleRate;
        this.processFrameCount = Math.ceil(this.sampleRate / 50);
        this.port.onmessage = this.onMessage.bind(this);
        this.wasmData = options.processorOptions.wasmData;
        this.pcmRingBuffer = null;
        this.caching = true;
        this.noMoreData = true;
        this.state = PLAY_CLOSED;
        this.encodedDataList = [];
        this.currentId = options.processorOptions.id;
    }

    sendEvent(event) {
        console.log("send event back to player", event);
        this.port.postMessage(event);
    }

    reset() {
        if (this.decoder) {
            this.opusModule._Opus_destroyDecoder(this.decoder);
            this.decoder = 0;
        }

        if (this.pcmBuffer) {
            this.opusModule._free(this.pcmBuffer);
            this.pcmBuffer = 0;
        }

        if (this.encodedData) {
            this.opusModule._free(this.encodedData);
            this.encodedData = 0;
        }

        this.opusModule = null;
        this.pcmRingBuffer = null;
        this.encodedDataList = null;
    }

    decodeOpus() {
        if (this.decoder === 0 || this.pcmBuffer === 0) {
            return;
        }

        while (this.encodedDataList.length > 0) {
            const encodedData = this.encodedDataList.shift();
            this.opusModule.writeArrayToMemory(encodedData, this.encodedData);
            const frameCount = this.opusModule._Opus_decode(this.decoder, this.encodedData, encodedData.length,
                this.pcmBuffer, this.processFrameCount * 2);

            if (frameCount) {
                const pcmArray = new Int16Array(this.opusModule.HEAP16.buffer, this.pcmBuffer, frameCount);
                let pcmBuf = Float32Array.from(pcmArray);
                pcmBuf = pcmBuf.map(value => {
                    return value / 32768;
                });

                if (this.pcmRingBuffer) {
                    this.pcmRingBuffer.push(pcmBuf);
                }
            }
        }
    }

    onMessage(event) {
        const ev = event.data;
        console.log("play worklet message", ev.evt & 0x00FF);
        switch (ev.evt & 0x00FF) {
            case PlayEvent.PLAYER_EVT_OPEN: {
                this.sampleRate = ev.data.sampleRate; // sample rate
                this.processFrameCount = Math.ceil(this.sampleRate / 50);
                this.pcmRingBuffer = new WeilaRingBuffer(this.sampleRate * 80, true);
                this.caching = true;
                this.noMoreData = false;
                this.state = PLAY_OPENING;

                if (this.opusModule == null) {
                    const Module = {};
                    Module['wasmBinary'] = this.wasmData;
                    opus(Module).then(value => {
                        if (this.state === PLAY_CLOSING) {
                            this.reset();
                            const rsp = buildPlayerRsp(PlayEvent.PLAYER_EVT_OPEN, {errCode: -2, errMsg: "编码器初始化失败"});
                            this.sendEvent(rsp);
                            return;
                        }

                        this.opusModule = value;
                        this.decoder = this.opusModule._Opus_initDecoder(this.sampleRate, 1);
                        if (!this.decoder) {
                            const rsp = buildPlayerRsp(PlayEvent.PLAYER_EVT_OPEN, {errCode: -3, errMsg: "编码器初始化失败"});
                            this.sendEvent(rsp);
                            this.reset();
                            this.state = PLAY_CLOSED;
                            return;
                        }

                        this.pcmBuffer = this.opusModule._malloc(this.processFrameCount * Int16Array.BYTES_PER_ELEMENT * 2);
                        if (!this.pcmBuffer) {
                            const rsp = buildPlayerRsp(PlayEvent.PLAYER_EVT_OPEN, {errCode: -4, errMsg: "申请缓存内存失败"});
                            this.sendEvent(rsp);
                            this.reset();
                            this.state = PLAY_CLOSED;
                            return;
                        }

                        this.encodedData = this.opusModule._malloc(256);
                        if (!this.encodedData) {
                            const rsp = buildPlayerRsp(PlayEvent.PLAYER_EVT_OPEN, {errCode: -5, errMsg: "申请编码内存失败"});
                            this.sendEvent(rsp);
                            this.reset();
                            this.state = PLAY_CLOSED;
                            return;
                        }

                        const rsp = buildPlayerRsp(PlayEvent.PLAYER_EVT_OPEN, {errCode: 0, errMsg: "ok"});
                        this.sendEvent(rsp);
                        this.state = PLAY_OPENED;
                    }).catch(reason => {
                        const rsp = buildPlayerRsp(PlayEvent.PLAYER_EVT_OPEN, {errCode: -6, errMsg: '出现异常:' + reason});
                        this.sendEvent(rsp);
                        this.reset();
                        this.state = PLAY_CLOSED;
                    });
                }else {
                    const rsp = buildPlayerRsp(PlayEvent.PLAYER_EVT_OPEN, {errCode: 0, errMsg: "ok"});
                    this.sendEvent(rsp);
                }
            }
            break;

            case PlayEvent.PLAYER_EVT_FEED: {
                if (this.state === PLAY_CLOSED || this.state === PLAY_CLOSING) {
                    break;
                }

                console.time("feed");
                const isLast = ev.data.isLast
                if (!isLast) {
                    const arrayBuffer = ev.data.buffer; // event
                    const lengthList = ev.data.lenList;

                    if (arrayBuffer) {
                        const compoArray = new CompoUint8Array(new Uint8Array(arrayBuffer), lengthList);
                        const arrayList = compoArray.parseArrayList();

                        if (arrayList) {
                            arrayList.forEach(value => {
                                this.encodedDataList.push(value);
                            })
                        }
                    }

                    this.decodeOpus();
                    console.timeEnd("feed")
                }else {
                    this.noMoreData = true;
                    this.caching = false;
                }
            }
            break;

            case PlayEvent.PLAYER_EVT_START: {
                this.state = PLAY_STARTING;
                this.caching = true;
                this.noMoreData = false;
                this.pcmRingBuffer.clear();
                this.encodedDataList = [];
            }
            break;

            case PlayEvent.PLAYER_EVT_PAUSE: {
                this.state = PLAY_PAUSING;
            }
            break;

            case PlayEvent.PLAYER_EVT_RESUME: {
                this.state = PLAY_RESUMING;
            }
            break;

            case PlayEvent.PLAYER_EVT_STOP: {
                this.state = PLAY_STOPPING;
            }
            break;

            case PlayEvent.PLAYER_EVT_CLOSE: {
                this.state = PLAY_CLOSING;
            }
            break;
        }
    }

    closePlayer() {
        console.log("close player..........");
        this.caching = true;
        this.noMoreData = false;
        this.pcmRingBuffer.clear();
        this.encodedDataList.splice(0, this.encodedDataList.length);
        this.state = PLAY_CLOSED;
        this.reset();
        const rsp = buildPlayerRsp(PlayEvent.PLAYER_EVT_CLOSE, {errCode: 0, errMsg: "ok"});
        this.sendEvent(rsp);
    }

    static get parameterDescriptors() {
        return [
            {name: 'gainChannel_0', defaultValue: 0.5, minValue: 0, maxValue: 1, automationRate: 'a-rate'},
            {name: 'gainChannel_1', defaultValue: 0.5, minValue: 0, maxValue: 1, automationRate: 'a-rate'}
        ]
    }

    process(inputs, outputs, params) {
        const output = outputs[0];
        const gain = params['gainChannel_0'];

        //console.log("process", this.state);

        if (this.state !== PLAY_STARTED) {
            if (this.state !== PLAY_STARTING) {
                if (this.state === PLAY_PAUSING) {
                    const rsp = buildPlayerRsp(PlayEvent.PLAYER_EVT_PAUSE, {errCode: 0, errMsg: "ok"});
                    this.sendEvent(rsp);
                    this.state = PLAY_PAUSED;
                } else if (this.state === PLAY_STOPPING) {
                    this.caching = true;
                    this.noMoreData = false;
                    this.pcmRingBuffer.clear();
                    this.encodedDataList.splice(0, this.encodedDataList.length);
                    this.state = PLAY_STOPPED;
                    const rsp = buildPlayerRsp(PlayEvent.PLAYER_EVT_STOP, {errCode: 0, errMsg: "ok"});
                    this.sendEvent(rsp);
                } else if (this.state === PLAY_RESUMING) {
                    this.state = PLAY_STARTED;
                    const rsp = buildPlayerRsp(PlayEvent.PLAYER_EVT_RESUME, {errCode: 0, errMsg: "ok"});
                    this.sendEvent(rsp);
                } else if (this.state === PLAY_CLOSING) {
                    this.closePlayer();
                }
                return true;
            }else {
                this.state = PLAY_STARTED;
                const rsp = buildPlayerRsp(PlayEvent.PLAYER_EVT_START, {errCode: 0, errMsg: "ok"});
                this.sendEvent(rsp);
            }
        }

        if (this.caching && !this.noMoreData) {
            if (this.pcmRingBuffer && this.pcmRingBuffer.frameAvailable >= this.processFrameCount * 20) {
                this.caching = false;
            }
            return true;
        }

        if (this.pcmRingBuffer && this.pcmRingBuffer.frameAvailable >= output.length) {
            output.forEach(value => {
                value.fill(0);
                this.pcmRingBuffer.pull(value);
            });
        }else {
            if (this.noMoreData) {
                if (this.pcmRingBuffer.frameAvailable) {
                    const buffer = new Float32Array(this.pcmRingBuffer.frameAvailable);
                    this.pcmRingBuffer.pull(buffer);
                    output.forEach(value => {
                        value.set(buffer, 0);
                    });
                }else {
                    const req = buildPlayerReq(PlayEvent.PLAYER_EVT_PLAY_END, {id: this.currentId})
                    this.sendEvent(req);
                    this.caching = true;
                    this.noMoreData = false;
                }
            }
        }

        return true;
    }
}

registerProcessor('player_worklet', WLPlayerWorklet);
